﻿namespace ClassyBlog.Infrastructure
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;

    using ExtensionMethods;

    public class ServiceContainer : Disposable, IServiceContainer
    {
        private static readonly IServiceContainer current =
            new ServiceContainer();

        private readonly IDictionary<RuntimeTypeHandle, ServiceRegistration>
            registrations =
            new Dictionary<RuntimeTypeHandle, ServiceRegistration>();

        private readonly object singletonsSyncLock = new object();

        private readonly IDictionary<RuntimeTypeHandle, object>
            singletons = new Dictionary<RuntimeTypeHandle, object>();

        public static IServiceContainer Current
        {
            [DebuggerStepThrough]
            get { return current; }
        }

        public virtual object Resolve(Type serviceType)
        {
            if (serviceType == null)
            {
                throw new ArgumentNullException("serviceType");
            }

            ServiceRegistration registration;
            var typeHandle = serviceType.TypeHandle;

            if (!registrations.TryGetValue(typeHandle, out registration))
            {
                return null;
            }

            object service;

            if (registration.Singleton)
            {
                if (!singletons.TryGetValue(typeHandle, out service))
                {
                    lock (singletonsSyncLock)
                    {
                        if (!singletons.TryGetValue(
                            typeHandle, out service))
                        {
                            singletons[typeHandle] = service =
                                registration.Factory(this);
                        }
                    }
                }
            }
            else
            {
                service = registration.Factory(this);
            }

            return service;
        }

        public IServiceContainer RegisterAsSingleton<TService>(
            Func<IServiceContainer, TService> factory)
        {
            return Register(factory, true);
        }

        public IServiceContainer RegisterAsTransient<TService>(
            Func<IServiceContainer, TService> factory)
        {
            return Register(factory, false);
        }

        protected virtual IServiceContainer Register<TService>(
            Func<IServiceContainer, TService> factory, bool singleton)
        {
            if (factory == null)
            {
                throw new ArgumentNullException("factory");
            }

            Func<IServiceContainer, object> wrapped = c => factory(c);

            var typeHandle = typeof(TService).TypeHandle;

            registrations[typeHandle] = new ServiceRegistration
                                            {
                                                Factory = wrapped,
                                                Singleton = singleton
                                            };

            return this;
        }

        protected override void DisposeCore()
        {
            singletons.Values
                      .OfType<IDisposable>()
                      .Each(d => d.Dispose());

            singletons.Clear();
            registrations.Clear();

            base.DisposeCore();
        }

        private sealed class ServiceRegistration
        {
            public Func<IServiceContainer, object> Factory { get; set; }

            public bool Singleton { get; set; }
        }
    }
}